Skip to content

feat(reports): Reports API cmdlets + Connect-Inforcer improvements (v0.5.0)#33

Merged
royklo merged 17 commits into
mainfrom
feat/reports-endpoints
Jun 30, 2026
Merged

feat(reports): Reports API cmdlets + Connect-Inforcer improvements (v0.5.0)#33
royklo merged 17 commits into
mainfrom
feat/reports-endpoints

Conversation

@royklo

@royklo royklo commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds 4 new Reports API cmdlets — Get-InforcerReportType, Invoke-InforcerReport,
    Get-InforcerReportRun, Save-InforcerReportOutput — wrapping /beta/reports/* with
    sync+save (default), -NoWait, -NoSave, and an -Open switch (allowlisted extensions
    only) that launches saved files via the OS default handler.
  • Connect-Inforcer now accepts any valid API key regardless of scope (envelope-shape
    detection); HTTP 401 errors on PowerShell 7 now surface a meaningful message; the Reports

Summary

  • Adds 4 new Reports API cmdlets — Get-InforcerReportType, Invoke-InforcerReport, Get-InforcerReportRun,
    Save-InforcerReportOutput — wrapping /beta/reports/* with sync+save (default), -NoWait, -NoSave, and an -Open
    switch (allowlisted extensions only) that launches saved files via the OS default handler.
  • Connect-Inforcer now accepts any valid API key regardless of scope (envelope-shape detection); HTTP 401 errors on
    PowerShell 7 now surface a meaningful message; the Reports catalog cache is best-effort primed on connect so TAB completion
    works on first attempt.
  • API error responses now include field-level details from the structured errors[] array (e.g. "Validation failed —
    tenants.includeTenants contains tenant X that is not in the API key's scope"
    ) instead of the generic placeholder;
    x-correlation-id is captured for support tickets.

Test plan

  • Invoke-Pester ./Tests/ -Output Detailed → 394 pass / 0 fail / 2 legit skips
  • Module loads cleanly: Import-Module ./module/InforcerCommunity.psd1 -Force → 20 cmdlets exported
  • Live API smoke (gitignored, local-only): ./Tests/Manual/Live-ApiSmoke.ps1 -BaseUrl '<region>' -TenantId <owned>
    24/24 PASS
  • Invoke-InforcerReport -Key ActiveUsers -TenantId <X> -OutputFormat csv -OutputPath ./tmp -Open → file lands on disk
    and opens in the OS handler
  • -Open allowlist denies extensions outside .csv .json .html .htm .pdf .xlsx .xls .xml .txt .zip .md .tsv
  • Connect-Inforcer succeeds with a Reports-scoped-only key (previously failed because /beta/baselines validation
    required Baselines.Read)
  • PSScriptAnalyzer on Reports files: zero real findings (ArgumentCompleter PSReviewUnusedParameter false-positives
    excluded — those params are framework contracts)

Release notes

See CHANGELOG.md § [0.5.0] - 2026-06-30. Module version bumped from 0.4.0 to 0.5.0 in module/InforcerCommunity.psd1.

royklo and others added 8 commits June 28, 2026 19:34
Invoke-InforcerApiRequest now interprets all three error shapes the Inforcer
API uses (app-layer {success, message, errors}, APIM gateway {statusCode,
message}, RFC 9110 ProblemDetails {type, title, status, traceId}) and pulls
a usable message out of each. The x-correlation-id response header is logged
to the verbose stream on every request and included in error messages on
failure for support tickets.

New private helpers:
  Get-InforcerHeaderValue           — case-insensitive header lookup across
                                      HttpResponseHeaders / WebHeaderCollection
                                      / IDictionary shapes
  Protect-InforcerApiKeyInText      — single redaction helper used wherever an
                                      exception or response body could leak the
                                      live API key into a user-facing error

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Connect-Inforcer used to validate against /beta/baselines and emit an empty
error on 401 in PowerShell 7 (the catch block checked [WebException] but PS7
raises HttpResponseException, so the status code stayed at 0). It also failed
outright for keys scoped only to non-Baselines endpoints (Reports.Read,
Assessments.Read, etc.).

The validation now uses Invoke-WebRequest -SkipHttpErrorCheck (PS7+) so 4xx
responses don't throw, and interprets the *envelope shape* alongside the
status code:

  * 200                                 → key valid + scope present
  * 4xx with Inforcer-app envelope      → APIM accepted the subscription, the
    {success/errorCode/errors}           Inforcer app rejected the scope. That
                                         is itself proof the key is valid.
  * 401 with APIM-gateway envelope      → subscription key rejected — emit a
    {statusCode, message} only           clear error with the API's text.

No scope is privileged for validation, so a user with Reports-only,
Assessments-only, Audit-only, etc. can now connect. Verified live against
api-uk.inforcer.com with a Reports.Read + Reports.Run key (no Baselines.Read).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four new public cmdlets cover the /beta/reports/* surface end-to-end:

  Get-InforcerReportType    Lists the catalog from GET /reports/types and
                             caches it in $script:InforcerReportTypeCache.
                             Filters by -Key, -Tag, -OutputFormat.

  Invoke-InforcerReport     Queues a run (POST /reports/runs), polls
                             /reports/runs/{id}/outputs until terminal, and
                             saves each output to -OutDir. Sync + save is the
                             default; -NoWait queues and returns immediately,
                             -NoSave polls without writing. Zip / broadcast
                             rules on -ReportType / -OutputFormat arrays.
                             Client-side guards: duplicate-pair dedup, Collate
                             vs collatable, unknown-parameter-key rejection.

  Get-InforcerReportRun     Lists runs (server cap 500 items / 7 days). -Wait
                             with -RunId polls /outputs to bypass the ~4-min
                             list-propagation lag; -IncludeOutputs embeds
                             outputs and uses the same lag fallback.

  Save-InforcerReportOutput Binary-safe download via Invoke-InforcerRawDownload.
                             Pipeline-friendly (-RunId + -OutputId from output
                             records). Uses Content-Disposition (incl. RFC 5987
                             filename*=UTF-8'') with cross-platform sanitization.

Dynamic -AssessmentId tab completion has three states with sub-millisecond
cache-hit latency after the first TAB:
  not connected               → <Run Connect-Inforcer first>
  no Assessments.Read scope   → <Assessments.Read API scope required>
                                (denial sentinel cached on 401/403)
  connected with scope        → friendly names with GUIDs inserted

Supporting changes:
  Resolve-InforcerReportTypeSchema   catalog validation + smart defaults
  Test-InforcerReportRunTerminal     single-poll probe (200 terminal, 404 not)
  Resolve-InforcerReportOutputFileName  Content-Disposition + RFC 5987 parser
  Invoke-InforcerRawDownload         binary-safe GET with envelope-aware errors
  Add-InforcerPropertyAliases        new ObjectTypes: ReportType/Run/Output
  InforcerCommunity.Format.ps1xml    4 new ListControl views
  InforcerCommunity.psd1             FunctionsToExport +4 → 20 total
  Disconnect-Inforcer                clears all $script:Inforcer*Cache* vars
                                      (broadened wildcard catches sentinels)
  Get-InforcerAssessment             primes the assessment cache as a
                                      side-effect so the completer is instant
  scripts/Test-AllCmdlets.ps1         entries for the 4 new cmdlets

Required API scopes: Reports.Read + Reports.Run for trigger flows; Reports.Read
alone for listing/downloading.

All shapes verified live against api-uk.inforcer.com beta (the catalog uses
supportedOutputFormats[] and requiredParameters[]; runs use runId, plural
reportTypes[] and outputFormats[]; outputs use id/reportType/format/sizeBytes;
no server-side fileName or contentType on output records).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
  docs/CMDLET-REFERENCE.md  Per-cmdlet sections for the 4 new cmdlets with
                             synopsis, parameter table, examples, fake example
                             output, and Required API scope(s) line. The
                             example outputs match the real API shape
                             (plural reportTypes/outputFormats arrays on runs,
                             minimal { RunId } on -NoWait, no per-record
                             fileName on outputs).

  docs/API-REFERENCE.md     New Reports endpoint section (5 endpoints) with
                             per-endpoint scope lines, request/response shape
                             tables, and the deliberate-404-indistinguishability
                             caveat on the outputs endpoint. ReportType,
                             ReportRun, ReportOutput schemas with field names
                             matching live API responses. New TOC entries.
                             Reports.Read + Reports.Run rows in Scope→Routes.
                             Per-cmdlet rows in Cmdlet→Endpoints→Required Scopes.

  README.md                 Cmdlet table extended with the 4 new cmdlets and
                             the existing Assessment cmdlets.

  CHANGELOG.md              [Unreleased] entry covering the Reports cmdlets,
                             the Connect/Disconnect/Invoke-InforcerApiRequest
                             improvements, and the dynamic completer.

  REPORTS-CMDLETS-HANDOFF.md  The implementation brief carried forward.

  Reports-API-Feedback.md   §1-10 + Q1-10 + features 1-7 from the original
                             two-day probe. §11-16 new findings from live
                             cmdlet testing:
                               §11 supportedOutputFormats vs outputFormats[]
                               §12 runId vs id naming inconsistency
                               §13 outputs use format/sizeBytes vs outputFormat
                                   /fileSize on input
                               §14 assessment-id is opaque string, not GUID
                               §15 4-min list lag also blocks -IncludeOutputs
                                   flows, not just polling
                               §16 collated outputs return tenantId:0 — needs
                                   null or a scope discriminator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ness

  Tests/Consistency.Tests.ps1
    - expectedCount 16 → 20; expectedParameters covers all 4 new cmdlets
    - No-silent-failure assertions for the 4 new cmdlets
    - Property-alias tests for ReportType/ReportRun/ReportOutput using the
      real API-shape field names (key/supportedOutputFormats/requiredParameters
      on types; runId/reportTypes/outputFormats on runs;
      id/reportType/format/sizeBytes on outputs)
    - Resolve-InforcerReportTypeSchema: 8 cases (valid pair, auto-default
      report-period, unknown type, unsupported format, collate on non-collatable,
      Assessment without -AssessmentId, Assessment with -AssessmentId, unknown
      parameter key)
    - Resolve-InforcerReportOutputFileName: 8 cases (plain filename=, quoted
      with spaces, RFC 5987 filename*=UTF-8'' percent-encoding, both forms with
      filename* winning, path-component stripping, reserved-char sanitization,
      empty/null fallback)
    - Get-InforcerHeaderValue: case-insensitive lookups across hashtable shapes
    - Test-InforcerReportRunTerminal: 200/404/error branches via Mock
    - Invoke-InforcerRawDownload: bytes + filename + correlation ID on 200,
      fallback DefaultFileName when Content-Disposition missing, 4xx surfaces
      the API-shaped message
    - Disconnect-Inforcer cache clearing across multiple $script:Inforcer*Cache*
      variables (including the sentinel companion)

    Tests passed: 124 / 124 (full Tests/ suite: 359 / 359)

  Tests/Manual/Test-AssessmentIdCompleter.ps1
    Empirical-test harness per REPORTS-CMDLETS-HANDOFF.md §5. Drives the
    completer scriptblock directly across the three permission states +
    the disconnect-clears-cache edge case. Validated live: 825ms first TAB
    (under 2s budget), 0ms cache hit; denial sentinel correctly caches on
    401/403; State 3 (not connected) returns the reconnect hint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four issues that bit a real first-time user on a Reports-only key:

1. Get-InforcerReportType -Key / -Tag / -OutputFormat had no tab completion
   despite the docs implying it. Added ArgumentCompleters that prefer the
   live catalog (after the first call) and fall back to an inline list of
   the known 25 report types / tags / formats so TAB works even before the
   cache is primed.

2. Get-InforcerReportType -Key TenantAuditReport showed `Parameters :` with
   a blank value for types that take no parameters. Now renders `(none)`.

3. -OutDir → -OutputPath on Invoke-InforcerReport and Save-InforcerReportOutput
   to match the existing module convention (Export-InforcerTenantDocumentation,
   Invoke-InforcerAssessment, Compare-InforcerEnvironments all use -OutputPath).

4. Tenant resolution on a Reports-only key produced two opaque HTTP 403
   stack traces followed by a misleading "No tenant found" message. The cmdlet
   now skips the /beta/tenants lookup entirely when every -TenantId is already
   a numeric Client Tenant ID (fast path), and on lookup failure surfaces a
   single actionable error:

     "The API key was rejected when looking up tenant 'X' against
      GET /beta/tenants (this endpoint requires the Tenants.Read scope, which
      a Reports-only key does not include). Pass the numeric Client Tenant ID
      directly — e.g. -TenantId 14436 — or re-key with Tenants.Read to enable
      name / GUID resolution."

   Verified live against api-uk.inforcer.com with the Reports-only key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SESSION-HANDOFF-REPORTS.md captures the state at the end of this feature
session so a fresh context can pick up cleanly. Includes:

  * Six-commit branch summary
  * What's verified live vs what's still untested
  * Next-session focus per the user's directive: broader testing, finding
    collection, iterative UX improvements
  * Locked-in design decisions a new context must NOT re-derive
  * Test keys + tenant IDs already exported in the user's shell history
  * Live-smoke + Pester regression one-liners

This file is intended to be deleted (or rewritten) once the PR is merged —
it has no purpose post-merge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Added validation for FileName parameter in Save-InforcerReportOutput.ps1 to ensure it is not null or empty.
- Improved output path handling to prevent writing to system directories with Test-InforcerSafeOutputPath.
- Introduced Format-InforcerErrorDetail function to render structured error messages from API responses.
- Created Get-InforcerReportTypeStaticKeys to provide static fallback keys for argument completers.
- Implemented comprehensive end-to-end smoke tests in Smoke-EndToEnd.ps1 to validate cmdlet functionality without live API.
- Developed Test-ReportTypeCompleter.ps1 to empirically test dynamic tab completion for report types and tags.
- Added Test-InforcerSafeOutputPath to ensure output paths do not point to restricted system directories.
- Enhanced error handling and output file management in Save-InforcerReportOutput.ps1.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands the InforcerCommunity PowerShell module with first-class support for the Reports API (/beta/reports/*), improves connection validation to accept any valid key regardless of scope, and upgrades API error handling to surface structured field-level details and correlation IDs for support.

Changes:

  • Adds four new public Reports cmdlets (catalog, run, poll, download) plus supporting private helpers (schema validation, safe downloads, filename parsing, terminal probing).
  • Updates Connect-Inforcer / Disconnect-Inforcer for scope-agnostic validation, cache hygiene, and best-effort report catalog priming for TAB completion.
  • Updates docs, formatting views, CI (run all Pester tests + add a smoke job), and introduces manual test harness scripts.

Reviewed changes

Copilot reviewed 42 out of 43 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
users/roy/downloads/ActiveUsers.json Adds a sample report output JSON (currently contains real tenant/user identifiers).
Tests/Manual/Test-ReportTypeCompleter.ps1 Manual harness for report type/tag/outputformat completers and cache edge cases.
Tests/Manual/Test-AssessmentIdCompleter.ps1 Manual harness for dynamic -AssessmentId completer behavior across permission states.
Tests/Manual/Smoke-EndToEnd.ps1 End-to-end mocked HTTP smoke scenario for Reports cmdlets behavior.
SESSION-HANDOFF-REPORTS.md Session handoff notes and testing reference material (currently includes live keys).
scripts/Test-AllCmdlets.ps1 Extends smoke validation to include the new Reports cmdlets.
REPORTS-CMDLETS-HANDOFF.md Implementation brief/handoff document for the Reports cmdlets design.
Reports-API-Feedback.md Captures empirical API quirks/bugs and feature requests for Reports endpoints.
README.md Updates exported cmdlet list to include Reports and other missing cmdlets.
module/Public/Save-InforcerReportOutput.ps1 New cmdlet to download report outputs to disk (pipeline-friendly).
module/Public/Invoke-InforcerReport.ps1 New cmdlet to queue, poll, download, and optionally open report outputs.
module/Public/Get-InforcerReportType.ps1 New cmdlet to fetch/cache/filter the report type catalog with completers.
module/Public/Get-InforcerReportRun.ps1 New cmdlet to list runs, optionally poll via outputs endpoint, and embed outputs.
module/Public/Get-InforcerAssessment.ps1 Primes assessment completion cache to support report assessment-id completion.
module/Public/Disconnect-Inforcer.ps1 Clears all Inforcer*Cache* variables on disconnect to prevent stale completers.
module/Public/Connect-Inforcer.ps1 Scope-agnostic key validation (envelope-shape detection) + primes Reports catalog cache.
module/Private/Test-InforcerSafeOutputPath.ps1 New safety helper to deny writes into OS/system directories.
module/Private/Test-InforcerReportRunTerminal.ps1 New helper to probe terminal state via GET /runs/{id}/outputs (404→200).
module/Private/Resolve-InforcerTenantId.ps1 Avoids leaking tenant IDs when multiple tenants match a name (privacy hardening).
module/Private/Resolve-InforcerReportTypeSchema.ps1 New catalog-driven client-side validation + smart defaults for report parameters.
module/Private/Resolve-InforcerReportOutputFileName.ps1 New RFC 5987-aware filename parser + filesystem-safe sanitization.
module/Private/Resolve-InforcerGraphEnrichment.ps1 Increases JSON depth to 100 for Graph batch request bodies.
module/Private/Protect-InforcerApiKeyInText.ps1 New helper to redact API keys from error/verbose text.
module/Private/Invoke-InforcerRawDownload.ps1 New binary-safe download helper with streaming-to-disk support.
module/Private/Invoke-InforcerAssessmentRun.ps1 Fixes disposal/stop behavior to avoid runspace/socket leaks on interruption.
module/Private/Invoke-InforcerApiRequest.ps1 Adds 3-envelope error parsing, structured errors[] rendering, and correlation-id capture.
module/Private/Get-InforcerSettingsCatalogPath.ps1 Ensures JSON writes use -Depth 100 (avoids truncation).
module/Private/Get-InforcerReportTypeStaticKeys.ps1 New central static fallback list for report type key completion.
module/Private/Get-InforcerHeaderValue.ps1 New helper for consistent header retrieval across PS host header shapes.
module/Private/Get-InforcerComparisonData.ps1 Ensures JSON serialization uses depth 100 (avoids truncation).
module/Private/Format-InforcerErrorDetail.ps1 New helper to render structured API validation errors into readable text.
module/Private/Compare-InforcerDocModels.ps1 Ensures JSON serialization uses depth 100 (avoids truncation).
module/Private/Add-InforcerPropertyAliases.ps1 Extends aliasing to Reports object types (ReportType/ReportRun/ReportOutput).
module/InforcerCommunity.psm1 Initializes progress ID seed and warns on module unload clearing session/caches.
module/InforcerCommunity.psd1 Bumps module to 0.5.0 and exports the new Reports cmdlets.
module/InforcerCommunity.Format.ps1xml Adds default views for ReportType/ReportRun/ReportOutput/ReportRunResult.
docs/CMDLET-REFERENCE.md Adds reference sections and examples for the new Reports cmdlets.
docs/api-schema-snapshot.json Updates schema snapshot metadata and includes Reports endpoints.
docs/API-REFERENCE.md Adds Reports section, scope mappings, and schemas for report types/runs/outputs.
CHANGELOG.md Adds 0.5.0 entry describing Reports feature set and related improvements.
.gitignore Updates ignored local scratch files and artifacts; reshapes ignore patterns.
.github/workflows/build-and-test.yml Runs all Pester tests, adds cmdlet smoke job, and improves analyzer messaging.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +362 to +367
# Convert tags array to comma-separated string for display
$tagsProp = $obj.PSObject.Properties['tags']
if ($tagsProp -and $tagsProp.Value -is [array]) {
$tagsProp.Value = ($tagsProp.Value | Where-Object { $_ }) -join ', '
}
AddAliasIfExists $obj 'Tags' 'tags'
Comment thread module/Public/Invoke-InforcerReport.ps1 Outdated
Comment on lines +40 to +42
.PARAMETER AssessmentId
Required when ReportType is 'Assessment'. The assessment GUID. Get-InforcerAssessment
lists available IDs.
Comment thread docs/CMDLET-REFERENCE.md Outdated
Comment thread CHANGELOG.md Outdated
- **Required scopes** — `Reports.Read` + `Reports.Run` (+ `Tenants.Read` only when `-TenantId` is a GUID or tenant name).
- **New cmdlet: `Get-InforcerReportRun`** — lists report runs from `GET /beta/reports/runs` (server caps the result at 500 items / last 7 days). `-Wait` with `-RunId` polls until the run is complete and bypasses a ~4-minute lag between completion and list visibility. `-IncludeOutputs` embeds each run's outputs.
- **New cmdlet: `Save-InforcerReportOutput`** — downloads a specific report output to disk via `GET /beta/reports/runs/{runId}/outputs/{outputId}`. Pipeline-friendly with `Invoke-InforcerReport -NoSave` and `Get-InforcerReportRun -IncludeOutputs`. Uses the server's `Content-Disposition` filename (with full Unicode support); `-FileName` overrides. Sanitizes Windows reserved names (`CON`, `PRN`, `AUX`, etc.) and caps length at 200 characters while preserving the extension.
- **Dynamic tab completion for `-AssessmentId` on `Invoke-InforcerAssessment`** — three permission-aware states: not connected (hint to run `Connect-Inforcer`), connected without scope (hint about `Assessments.Read`, denial cached so subsequent TABs are instant), connected with scope (friendly names in the dropdown, GUID inserted on selection, `name — GUID` shown as the tooltip).
Comment thread SESSION-HANDOFF-REPORTS.md Outdated
Comment on lines +87 to +91
| Key | Scopes | Use case |
|---|---|---|
| `4a832801726549079d90a8ca293a28e7` | `Reports.Read` + `Reports.Run` | Default test key — exercises scope-agnostic Connect + completer State 2 (denied scope) |
| `cd4f9bca64c74f7a98e8f20f13b604e1` | Above + `Assessments.Read` | Exercises completer State 1 (real assessments) + Assessment-type reports |
| `f0c47ea78c114cc081a355e6bb8053d2` | (rejected by APIM) | Tests the genuine "invalid subscription" path |
Comment thread SESSION-HANDOFF-REPORTS.md Outdated
**Live smoke (one-liner):**

```powershell
INFORCER_API_KEY='cd4f9bca64c74f7a98e8f20f13b604e1' pwsh -NoProfile -Command '
Comment thread users/roy/downloads/ActiveUsers.json Outdated
@@ -0,0 +1 @@
{"title":"Active Users","rows":[{"UserPrincipalName":"roy@inforcedbyroy.onmicrosoft.com","TenantDnsName":"inforcedbyroy.onmicrosoft.com","Id":"80808797-4441-4fb3-9c06-7974ad25c3dc"},{"UserPrincipalName":"breakglass@inforcedbyroy.onmicrosoft.com","TenantDnsName":"inforcedbyroy.onmicrosoft.com","Id":"d2c64e5d-0538-463c-a6ef-e9c88a28de4f"},{"UserPrincipalName":"jon@inforcedbyroy.onmicrosoft.com","TenantDnsName":"inforcedbyroy.onmicrosoft.com","Id":"a9d8c055-7c37-468c-944d-991e469461b8"},{"UserPrincipalName":"Tim@inforcedbyroy.onmicrosoft.com","TenantDnsName":"inforcedbyroy.onmicrosoft.com","Id":"8bdcf4c7-3b4a-4cb8-996d-c5af3c7c3a26"},{"UserPrincipalName":"Lewis@inforcedbyroy.onmicrosoft.com","TenantDnsName":"inforcedbyroy.onmicrosoft.com","Id":"4e87b958-384b-4fb3-9103-4b040ca6e192"},{"UserPrincipalName":"JonTest@inforcedbyroy.onmicrosoft.com","TenantDnsName":"inforcedbyroy.onmicrosoft.com","Id":"8b085767-0af3-4267-8891-3217dd8e5c36"},{"UserPrincipalName":"demo@inforcedbyroy.onmicrosoft.com","TenantDnsName":"inforcedbyroy.onmicrosoft.com","Id":"1ae1317b-4136-45fe-93dc-92c5e2394c1a"},{"UserPrincipalName":"mac@inforcedbyroy.onmicrosoft.com","TenantDnsName":"inforcedbyroy.onmicrosoft.com","Id":"07c145c4-1235-4295-8c48-f04ac9e4a26c"}]} No newline at end of file
Comment thread module/Public/Connect-Inforcer.ps1 Outdated
Copilot AI review requested due to automatic review settings June 30, 2026 06:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 42 out of 43 changed files in this pull request and generated 4 comments.

Comment thread module/Public/Get-InforcerReportType.ps1
Comment on lines +157 to +159
foreach ($e in $script:InforcerReportTypeCache) {
if (($e.PSObject.Properties['key'].Value -as [string]) -ieq $rtStr) { $entry = $e; break }
}
Comment on lines +26 to +31
.PARAMETER ReportPeriod
Optional integer days. When set, merged into parameters as 'report-period'=<value>.
.PARAMETER AssessmentId
Optional GUID. When set, merged into parameters as 'assessment-id'=<value>. Required
for ReportType='Assessment'.
.PARAMETER Collate
Comment on lines +48 to +50
# Normalise the ApiKey to SecureString. Priority: explicit -ApiKey → $env:INFORCER_API_KEY
# → interactive prompt. Plain strings are zeroed after conversion.
function ConvertTo-SafeApiKey {

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 42 out of 43 changed files in this pull request and generated 3 comments.

Comment on lines +214 to +216
if ($PSBoundParameters.ContainsKey('AssessmentId')) {
$finalParams['assessment-id'] = $AssessmentId.ToString()
}
Comment on lines +159 to +163
ContentType = $download.ContentType
CorrelationId = $download.CorrelationId
}
$result.PSObject.TypeNames.Insert(0, 'InforcerCommunity.ReportRunResult')

Comment on lines +27 to +28
$pattern = [regex]::new([regex]::Escape($ApiKey), 'Compiled')
$pattern.Replace($Text, '[REDACTED]')
Copilot AI review requested due to automatic review settings June 30, 2026 12:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 39 out of 40 changed files in this pull request and generated 3 comments.

Comment thread docs/API-REFERENCE.md
Comment on lines +392 to +393
| 200 | `{ data: [ReportOutput, ...] }` | Run is in a terminal state (`completed` or `completedWithErrors`) and outputs are available. |
| 200 | `{ data: [] }` | Run is visible to the API key, but **no outputs are within the key's tenant scope.** Collated outputs (covering multiple tenants) are only returned when the key covers every tenant the run targeted. |
Comment thread Reports-API-Feedback.md Outdated
@@ -0,0 +1,175 @@
# Reports API — Feedback & Questions

From two empirical-probing sessions against `api-uk.inforcer.com` (UK region, beta endpoints) on 2026-06-25 and 2026-06-26. All findings reproduced at least twice unless noted. Test scope: API key with `Reports.Read` + `Reports.Trigger` scopes, targeting tenants `14436` and `18159`.
Comment on lines +345 to +347
# Keep raw 'tags' shape intact (array OR comma-separated string from the API);
# downstream filters (e.g. Get-InforcerReportType -Tag) rely on the raw shape,
# and the Format.ps1xml view joins for display.
@royklo royklo merged commit 7d59193 into main Jun 30, 2026
3 checks passed
@royklo royklo deleted the feat/reports-endpoints branch June 30, 2026 12:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants